

package com.junjie.index12306.biz.ticketservice.service.handler.ticket.select;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.junjie.index12306.biz.ticketservice.common.enums.VehicleSeatTypeEnum;
import com.junjie.index12306.biz.ticketservice.common.enums.VehicleTypeEnum;
import com.junjie.index12306.biz.ticketservice.dao.entity.TrainStationPriceDO;
import com.junjie.index12306.biz.ticketservice.dao.mapper.TrainStationPriceMapper;
import com.junjie.index12306.biz.ticketservice.dto.domain.PurchaseTicketPassengerDetailDTO;
import com.junjie.index12306.biz.ticketservice.dto.req.PurchaseTicketReqDTO;
import com.junjie.index12306.biz.ticketservice.remote.UserRemoteService;
import com.junjie.index12306.biz.ticketservice.remote.dto.PassengerRespDTO;
import com.junjie.index12306.biz.ticketservice.service.handler.ticket.dto.SelectSeatDTO;
import com.junjie.index12306.biz.ticketservice.service.handler.ticket.dto.TrainPurchaseTicketRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.junjie.index12306.biz.ticketservice.service.SeatService;
import com.junjie.index12306.framework.starter.convention.exception.RemoteException;
import com.junjie.index12306.framework.starter.convention.exception.ServiceException;
import com.junjie.index12306.framework.starter.convention.result.Result;
import com.junjie.index12306.framework.starter.designpattern.strategy.AbstractStrategyChoose;
import com.junjie.index12306.frameworks.starter.user.core.UserContext;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 购票时列车座位选择器
 *
 * 
 */
@Slf4j
@Component
@RequiredArgsConstructor
public final class TrainSeatTypeSelector {

    private final SeatService seatService;
    private final UserRemoteService userRemoteService;
    private final TrainStationPriceMapper trainStationPriceMapper;
    private final AbstractStrategyChoose abstractStrategyChoose;
    private final ThreadPoolExecutor selectSeatThreadPoolExecutor;

    /**
     * 这里核心逻辑：匹配座位设置价格 + 锁定座位
     */
    public List<TrainPurchaseTicketRespDTO> select(Integer trainType, PurchaseTicketReqDTO requestParam) {
        // 1、获取乘车人列表
        List<PurchaseTicketPassengerDetailDTO> passengerDetails = requestParam.getPassengers();
        // 2、将乘车人 根据座位类型分类 拆分出来
        Map<Integer, List<PurchaseTicketPassengerDetailDTO>> seatTypeMap = passengerDetails.stream()
                .collect(Collectors.groupingBy(PurchaseTicketPassengerDetailDTO::getSeatType));
        List<TrainPurchaseTicketRespDTO> actualResult = new CopyOnWriteArrayList<>();
        // 3.1、如果座位类型为2个及以上 就多线程匹配座位
        if (seatTypeMap.size() > 1) {
            List<Future<List<TrainPurchaseTicketRespDTO>>> futureResults = new ArrayList<>();
            seatTypeMap.forEach((seatType, passengerSeatDetails) -> {
                // 线程池参数如何设置？ 
                Future<List<TrainPurchaseTicketRespDTO>> completableFuture = selectSeatThreadPoolExecutor
                        .submit(() -> distributeSeats(trainType, seatType, requestParam, passengerSeatDetails));
                futureResults.add(completableFuture);
            });
            // keypoint 使用并行流的前提是硬件支持，如果单核 CPU，只会存在并发处理，而不会并行
            //  ，并行流极端情况下有坑，线程安全问题(使用线程安全的集合类)，执行无序，在项目中使用了并行流真正执行时，
            //  并非一定是并行的。因为默认并行数为当前运行环境的 CPU 核数 - 1，
            //  如果项目中其它并行流的任务执行耗时，会占据对应资源，使得最后还是通过主线程执行任务。
            futureResults.parallelStream().forEach(completableFuture -> {
                try {
                    actualResult.addAll(completableFuture.get());
                } catch (Exception e) {
                    throw new ServiceException("站点余票不足，请尝试更换座位类型或选择其它站点");
                }
            });
        } else {
            // 3.2、如果所有乘车人座位类型只有一个 就直接单线程匹配座位就行了
            seatTypeMap.forEach((seatType, passengerSeatDetails) -> {
                List<TrainPurchaseTicketRespDTO> aggregationResult = distributeSeats(trainType, seatType, requestParam, passengerSeatDetails);
                actualResult.addAll(aggregationResult);
            });
        }
        // 4、判断座位数量和乘车人数量是否匹配 不匹配就说明匹配不到座位
        if (CollUtil.isEmpty(actualResult) || !Objects.equals(actualResult.size(), passengerDetails.size())) {
            throw new ServiceException("站点余票不足，请尝试更换座位类型或选择其它站点");
        }
        // 5、获取乘车人ID列表
        List<String> passengerIds = actualResult.stream()
                .map(TrainPurchaseTicketRespDTO::getPassengerId)
                .collect(Collectors.toList());
        Result<List<PassengerRespDTO>> passengerRemoteResult;
        List<PassengerRespDTO> passengerRemoteResultList;
        try {
            // 6、根据乘车人 ID 集合查询乘车人列表
            passengerRemoteResult = userRemoteService.listPassengerQueryByIds(UserContext.getUsername(), passengerIds);
            if (!passengerRemoteResult.isSuccess() || CollUtil.isEmpty(passengerRemoteResultList = passengerRemoteResult.getData())) {
                throw new RemoteException("用户服务远程调用查询乘车人相信信息错误");
            }
        } catch (Throwable ex) {
            // 先打印日志，再继续往外抛异常，很细节
            if (ex instanceof RemoteException) {
                log.error("用户服务远程调用查询乘车人相信信息错误，当前用户：{}，请求参数：{}", UserContext.getUsername(), passengerIds);
            } else {
                log.error("用户服务远程调用查询乘车人相信信息错误，当前用户：{}，请求参数：{}", UserContext.getUsername(), passengerIds, ex);
            }
            throw ex;
        }
        // 7、遍历 乘车人座位集合
        actualResult.forEach(each -> {
            String passengerId = each.getPassengerId();
            // 8、给乘车人座位集合 塞入具体的乘车人数据
            passengerRemoteResultList.stream()
                    .filter(item -> Objects.equals(item.getId(), passengerId))
                    .findFirst()
                    .ifPresent(passenger -> {
                        each.setIdCard(passenger.getIdCard());
                        each.setPhone(passenger.getPhone());
                        each.setUserType(passenger.getDiscountType());
                        each.setIdType(passenger.getIdType());
                        each.setRealName(passenger.getRealName());
                    });
            // 9、查出价格并设置
            LambdaQueryWrapper<TrainStationPriceDO> lambdaQueryWrapper = Wrappers.lambdaQuery(TrainStationPriceDO.class)
                    .eq(TrainStationPriceDO::getTrainId, requestParam.getTrainId())
                    .eq(TrainStationPriceDO::getDeparture, requestParam.getDeparture())
                    .eq(TrainStationPriceDO::getArrival, requestParam.getArrival())
                    .eq(TrainStationPriceDO::getSeatType, each.getSeatType())
                    .select(TrainStationPriceDO::getPrice);
            TrainStationPriceDO trainStationPriceDO = trainStationPriceMapper.selectOne(lambdaQueryWrapper);
            each.setAmount(trainStationPriceDO.getPrice());
        });
        // 10、锁定选中以及沿途车票状态
        seatService.lockSeat(requestParam.getTrainId(), requestParam.getDeparture(), requestParam.getArrival(), actualResult);
        return actualResult;
    }


    /**
     * 给乘车人分座位
     * 以下流程不包含购票人数大于等于三人（大于等于三人就要进行拆座位）以及在线选座等流程。
     * ● 如果购票人数为两人，购买同一车厢，座位优先检索两人相邻座位并排分配。
     * ● 假设当前正在检索的车厢不满足两人并排，就执行搜索全部满足两人并排的车厢。
     * ● 如果搜索了所有车厢还是没有两人并排做的座位，那么执行同车厢不相邻座位。
     * ● 如果所有车厢都是仅有一个座位，就开始执行最后降级操作，不同车厢分配。
     */
    private List<TrainPurchaseTicketRespDTO> distributeSeats(Integer trainType, Integer seatType, PurchaseTicketReqDTO requestParam, List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails) {
        // 构建Key 如 CAR + FIRST_SLEEPER 拼接
        String buildStrategyKey = VehicleTypeEnum.findNameByCode(trainType) + VehicleSeatTypeEnum.findNameByCode(seatType);
        SelectSeatDTO selectSeatDTO = SelectSeatDTO.builder()
                .seatType(seatType)
                .passengerSeatDetails(passengerSeatDetails)
                .requestParam(requestParam)
                .build();
        try {
            // 策略模式 根据 列车类型+座位类型 具体选择座位
            // 现在只有 高铁 一等座 二等座 商务座 三种策略
            return abstractStrategyChoose.chooseAndExecuteResp(buildStrategyKey, selectSeatDTO);
        } catch (ServiceException ex) {
            throw new ServiceException("当前车次列车类型暂未适配，请购买G35或G39车次");
        }
    }
}
